/*
 * Copyright (c) 2013, SRI International
 * All rights reserved.
 * Licensed under the The BSD 3-Clause License;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 * 
 * http://opensource.org/licenses/BSD-3-Clause
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the aic-web-praise nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.sri.ai.lpitools.executionengine.server.components.processor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.apache.log4j.Logger;
import org.json.simple.parser.ParseException;

import com.sri.ai.brewer.api.Parser;
import com.sri.ai.grinder.parser.antlr.AntlrGrinderParserWrapper;
import com.sri.ai.lpitools.executionengine.server.callback.CallBackClient_Interface;
import com.sri.ai.lpitools.executionengine.server.components.queryListener.JustificationListenerImpl;
import com.sri.ai.lpitools.executionengine.server.components.queryListener.QueryStepListener;
import com.sri.ai.lpitools.executionengine.server.components.queryListener.TraceListenerImpl;
import com.sri.ai.lpitools.executionengine.server.components.queryListener.TreeNodeQueue;
import com.sri.ai.praise.lbp.LBPFactory;
import com.sri.ai.praise.lbp.LBPQueryEngine;
import com.sri.ai.praise.lbp.LBPQueryEngine.QueryOptions;
import com.sri.ai.praise.model.Model;
import com.sri.ai.praise.model.Model.ModelError;
import com.sri.ai.praise.model.Model.ModelException;

public class ModelProcessor {
	protected Parser parser;

	private static final Logger logger = Logger.getLogger(ModelProcessor.class.getName());
	/**
	 * Constructor for the model processor class
	 * It sets all the need information
	 */
	public ModelProcessor(){
		parser = new AntlrGrinderParserWrapper();
	}
	
	/**
	 * This function takes in a model and test that it is valid
	 * @param modelPart
	 * @return
	 * @throws ParseException
	 */
	public String checkModel(String modelPart) throws ParseException{
		
		logger.trace("In model processor check model");			
		modelPart = cleanModel(modelPart);
		
		List<String> mE = new ArrayList<String>();
		Model model = null;
		boolean allLegal = true;
		String modelString = "model(" + modelPart + ")";
		//try and make a model if it errors out then get errors and set flag to build error json string
		try{
			model = new Model(parser.parse(modelString),new HashSet<String>());
		} catch (ModelException error) {
			allLegal = false;
			List<ModelError> s = error.getErrors();
			for(int i = 0; i<s.size(); i++){
				mE.add(s.get(i).getErrorType().toString());
			}
		}

		//call method to build resulting string, either a valid model statement or a String containing the errors that where found
		String result = buildResult(model, allLegal, mE);

		return result;
	}

	/**
	 * this is the function called when we want to run a query against a model.
	 * @param modelKey
	 * @param query
	 * @param client
	 * @param panelID
	 * @return
	 * @throws ParseException
	 */
	public String runQuery(String user, String modelUnFormatedString, String evidenceKey, String query, QueryOptions queryOptions, final CallBackClient_Interface client, String clientID) throws ParseException {
		logger.trace("In model processor run query");
		logger.trace("Client " + clientID + " is running " + query + " against " + modelUnFormatedString );
		
		modelUnFormatedString = cleanModel(modelUnFormatedString);
		
		String evidence = evidenceKey;
		if(query == null){
			return "Error, the query was bad and could not be found in the COBRA Server";
		}
		if(evidenceKey.equalsIgnoreCase("")){
			evidence = null;
		}

		//create array to hold any errors
		List<String> mE = new ArrayList<String>();
		Model model = null;
		boolean allLegal = true;
		String modelString = "model(" + modelUnFormatedString + ")";

		//try and make a model if it errors out then get errors and set flag to build error json
		try{
			model = new Model(parser.parse(modelString),new HashSet<String>());
		} catch (ModelException error) {
			allLegal = false;
			List<ModelError> s = error.getErrors();
			for(int i = 0; i<s.size(); i++){
				mE.add(s.get(i).getErrorType().toString());
			}
		}
			
		final List<Long> timeList = new ArrayList<Long>();
		final List<String> stepList = new ArrayList<String>();
		LBPQueryEngine queryEngine = LBPFactory.newLBPQueryEngine();
		String queryUUID = queryEngine.newQueryUUID(queryOptions);
		QueryStepListener queryListener = new QueryStepListener(timeList, stepList, client, clientID);
		queryEngine.addQueryStepListener(queryListener);
		long start = System.currentTimeMillis();
		//create trace and justification listeners and initialize them.
		//IMPORTANT: Queue must be set BEFORE client.
		//This is due to the fact that a listener sets its queue's client to match its client.
		//This will likely be fixed in the redesign.
		JustificationListenerImpl jListener = new JustificationListenerImpl();
		TraceListenerImpl tListener = new TraceListenerImpl();
		TreeNodeQueue queue = new TreeNodeQueue();
		jListener.setQueue(queue);
		tListener.setQueue(queue);
		jListener.setClient(client);
		tListener.setClient(client);
		jListener.setClientID(clientID);	
		tListener.setClientID(clientID);
		
		jListener.setMyQuerysUUID(queryUUID);
		tListener.setMyQuerysUUID(queryUUID);
		queryEngine.addJustificationListener(jListener);
		queryEngine.addTraceListener(tListener);

		try{
			String resultString  = queryEngine.queryBeliefOfRandomVariable(queryUUID, query, model.getModelDeclaration(), evidence);
			long end = System.currentTimeMillis();
		
		
			long runTime = end - start;
		
			if(!allLegal || resultString == null){
				return "Error";
			} else {
				logger.debug(resultString);
			}
			List<Long> timeListResult = queryListener.getTimeList();
			List<String> stepListResult = queryListener.getStepList();
			try {

				String result = "{ \"result\": \"" + resultString.replace("\\", "\\\\") + "\", \"time\": \"" + runTime + "\", \"timeValues\": [\"" ;
				result = result + processTimeValues(timeListResult) + "\"], \"timeTitles\": [\"";
				result = result + processStepValues(stepListResult) + "\"], \"clientID\": \"" + clientID + "\"}";
		
				//wait for queue to send last batch
				if(queue.isSendScheduled()) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}
				
				return result;
			} catch (IllegalArgumentException e1) {
				e1.printStackTrace();
				return "Error";
			} 
		} catch(Exception e){
		
		    StringBuilder result = new StringBuilder("");
		    result.append(e.toString());
		    String NEW_LINE = "\n";
		    result.append(NEW_LINE);

		    //add each element of the stack trace
		    for (StackTraceElement element : e.getStackTrace() ){
		    	result.append( element );
		    	result.append( NEW_LINE );
		    }
		    Throwable t = e.getCause();
		    result.append( "Caused by: " +  t.getMessage());
		    result.append( NEW_LINE );
		    for (StackTraceElement element : t.getStackTrace() ){
			      result.append( element );
			      result.append( NEW_LINE );
			}
			return "Got the following exception stack trace \n" + result.toString() ;
		}

	}
	
	/**
	 * This function is used to process a list of time values so they can be returned to the client
	 * @param timeValue
	 * @return
	 */
	private String processTimeValues(List<Long> timeValue){
		String result = "";
		for(int i = 0; i<timeValue.size(); i++){
			Long value = timeValue.get(i);
			result = result + value;
			if((i+1) < timeValue.size()){
				result = result + "\", \"" ;
			}
		}
		return result;
	}
	
	/**
	 * This function is used to process a list of step values so they can be returned to the client
	 * @param stepValue
	 * @return
	 */
	private String processStepValues(List<String> stepValue){
		String result = "";
		for(int i = 0; i<stepValue.size(); i++){
			String value = stepValue.get(i);
			result = result + value;
			if((i+1) < stepValue.size()){
				result = result + "\", \"" ;
			}
		}
		return result;
	}	

	/**
	 * This function builds the resulting that eithershows that the model is valid or a list of erros
	 * @param model
	 * @param result
	 * @param mE
	 * @return
	 */
	private String buildResult(Model model, boolean result, List<String> mE){

		//create the resulting string, either set result to true if there where no errors or false if there where errors in the model
		String resultString = "";
		if(result){
			resultString = "Model is valid";
		} else {
			resultString = "Error we got the following errors when validating the model:\n";
			for(int i = 0; i<mE.size(); i++){
				String error = mE.get(i);
				resultString = resultString + error;
				if((i+1) < mE.size()){
					resultString = resultString + "\n";
				}
			}
		}
		return resultString;
	}
	
	/**
	 * This function is used to remove any formating text from the model
	 * @param model
	 * @return
	 */
	private String cleanModel(String model){
		model = model.replace("\n", "");
		model = model.replace("\t", "");
		model = model.replace("\r", "");
		
		return model;
	}
	
}
